package cn.line5.seam.webapp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

import cn.line5.seam.annotations.AjaxRemote;
import cn.line5.seam.annotations.Request;
import cn.line5.seam.cache.ComponentCache;
import cn.line5.seam.cache.FilterCache;
import cn.line5.seam.component.Component;
import cn.line5.seam.exception.SeamException;
import cn.line5.seam.filter.FilterServletContext;
import cn.line5.seam.filter.RemoteFilter;
import cn.line5.seam.util.ReflectUtil;

/**
 * <h1>资源请求处理Servlet类</h1>
 * 
 * @author Liu,Xiaojun
 * @since JDK1.6
 */
public class ResourcesServlet extends HttpServlet implements Servlet {

	private static final long serialVersionUID = 4650992298764253319L;

	@SuppressWarnings("unchecked")
	@Override
	public void service(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String path = request.getPathInfo();
		response.setCharacterEncoding("UTF-8");
		if (path != null && path.startsWith("/execute")) { // ajax请求
			String[] action = path.substring(9).split("\\.");
			if (!ComponentCache.componentClass.containsKey(action[0]))
				throw new SeamException(
						"Can't find the action class by named '" + action[0]
								+ "'.");
			Component component = ComponentCache.componentClass.get(action[0]);
			Object o = component.getComponent();
			String line = null;
			StringBuffer sb = new StringBuffer();
			BufferedReader reader = request.getReader();
			while ((line = reader.readLine()) != null) {
				sb.append(line);
			}
			List<Map<String, Object>> paramList = new ArrayList<Map<String, Object>>();
			if (sb.length() > 0) {
				String[] params = sb.toString().split("@LSEAM-FGF@");
				for (String param : params)
					paramList.add((HashMap<String, Object>) JSONObject.toBean(
							JSONObject.fromObject(param), HashMap.class));
			}
			Method[] methods = o.getClass().getDeclaredMethods();
			Object obj = null;
			boolean haveMethod = false;
			for (Method method : methods) {
				if (method.getAnnotation(AjaxRemote.class) != null
						&& method.getName().equals(action[1])
						&& method.getParameterTypes().length == paramList
								.size()) {
					Class<?>[] classes = method.getParameterTypes();
					Object[] _params = new Object[paramList.size()];
					if(classes.length > 0)
						for (int i = 0; i < classes.length; i++) {
							Map<String, Object> param = paramList.get(i);
							if (param.get("type").equals("java.lang.Object")) {
								_params[i] = JSONObject
										.toBean(JSONObject.fromObject(param
												.get("value")), classes[i]);
								haveMethod = true;
							} else if (param.get("type").equals("java.lang.Object[]")) {
								_params[i] = getArrayValue(classes[i], JSONArray
										.fromObject(param.get("value")).toArray());
								haveMethod = true;
							} else {
								_params[i] = param.get("value");
								haveMethod = true;
							}
						}
					else
						haveMethod = true;
					if (haveMethod) {
						FilterServletContext context = new FilterServletContext();
						context.setComponentName(action[0]);
						context.setMethodName(action[1]);
						context
								.setRequestType(FilterServletContext.REQUEST_TYPE_AJAX);
						context.setRequest(request);
						context.setResponse(response);
						try {
							if (!RemoteFilter.createRemoteFilter().filter(
									context))
								return;
						} catch (Exception e) {
							throw new SeamException(
									"Can't use the RemoteFilter by named '"
											+ FilterCache.filterClass + "'.", e);
						}
						// 写入request对象
						if (component.getRequestField() != null
								&& component.getRequestField().size() > 0) {
							Iterator<Entry<String, Request>> t = component
									.getRequestField().entrySet().iterator();
							while (t.hasNext()) {
								String requestFieldName = t.next().getKey();
								try {
									Field f = component.getClazz().getDeclaredField(
											requestFieldName);
									if (!f.getType()
											.getName()
											.equals("javax.servlet.http.HttpServletRequest")) {
										throw new SeamException(
												"The Request field '"
														+ requestFieldName
														+ "' must defined the type 'javax.servlet.http.HttpServletRequest'");
									}
									f.setAccessible(true);
									ReflectUtil.setFieldValue(requestFieldName, o, request);
								} catch (NoSuchFieldException e) {
									throw new SeamException("Can't find the field named '"
											+ requestFieldName + "' in the "
											+ component.getClazz().getName() + ".", e);
								}
							}
						}
						try {
							obj = method.invoke(o, _params);
						} catch (IllegalArgumentException e) {
							throw new SeamException(
									"The method running error by named '"
											+ action[1] + "' in the "
											+ o.getClass().getName()
											+ ".", e);
						} catch (IllegalAccessException e) {
							throw new SeamException(
									"The method running error by named '"
											+ action[1] + "' in the "
											+ o.getClass().getName()
											+ ".", e);
						} catch (InvocationTargetException e) {
							throw new SeamException(
									"The method running error by named '"
											+ action[1] + "' in the "
											+ o.getClass().getName()
											+ ".", e);
						}
						break;
					}
				}
			}
			if (haveMethod) {
				PrintWriter write = response.getWriter();
				write.write(getReturnValue(obj));
				write.close();
			} else
				throw new SeamException("Can't find the method by named '"
						+ action[1] + "' in the "
						+ o.getClass().getName() + ".");
		} else {// js文件请求
			if (path != null && "/lseam.js".equals(path))
				path = "$resource_lseam";
			else if (path != null && "/json.js".equals(path))
				path = "$resource_json";
			else if (path != null && path.length() > 0)
				path = path.substring(1);
			List<String> list = getResource(path);
			PrintWriter write = response.getWriter();
			write.write("/**\n * " + path
					+ ".js\n * created by LSEAM\n */\n\n\n");
			for (String str : list)
				write.write(str);
			if ("$resource_lseam".equals(path))
				write.write("Lseam.resourcePath = \""
						+ request.getContextPath() + request.getServletPath()
						+ "\";\n");
			write.write("/**\n * " + path + ".js\n * created at " + new Date()
					+ "\n */\n\n\n");
			write.close();
		}
		return;
	}

	private List<String> getResource(String name) throws IOException {
		List<String> list = new ArrayList<String>();
		if (name == null || name.length() <= 0)
			return list;
		if (name.startsWith("$resource_")) {
			String temp;
			BufferedReader br = new BufferedReader(new InputStreamReader(this
					.getClass().getResourceAsStream(name), "UTF-8"));
			while ((temp = br.readLine()) != null)
				list.add(temp + "\n");
			br.close();
			return list;
		}
		String[] components = name.split("&");
		Map<String, Class<?>> typeMap = new HashMap<String, Class<?>>();
		Map<String, Object> componentMap = new HashMap<String, Object>();
		for (String componentName : components) {
			// 如果组件不存在
			if (!ComponentCache.componentClass.containsKey(componentName))
				throw new SeamException(
						"Can't find the action class by named '"
								+ componentName + "'.");
			if (componentMap.containsKey(componentName))
				continue;
			Component component = ComponentCache.componentClass
					.get(componentName);
			Method[] methods = component.getClazz().getMethods();
			list.add("Lseam.type." + componentName + " = function() {\n");
			list.add("  this.__callback = new Object();\n");
			list.add("  this.name = \"" + componentName + "\";\n");
			for (Method method : methods) {
				if (method.getAnnotation(AjaxRemote.class) != null) { // @AjaxRemote注解方法
					StringBuffer jsMethod = new StringBuffer("  Lseam.type.")
							.append(componentName).append(".prototype.")
							.append(method.getName()).append(" = function(");
					if (!isBasicType(method.getReturnType())
							&& !typeMap.containsKey(method.getReturnType()
									.getName())) // 不是基本类型 and 不在Map中
						typeMap.put(method.getReturnType().getName(), method
								.getReturnType());
					Class<?>[] classes = method.getParameterTypes();
					String parameter = "";
					for (int i = 0; i < classes.length; i++) {
						parameter += (i > 0 ? ", " : "") + "p" + i;
						Class<?> clazz = classes[i];
						if (!isBasicType(clazz)
								&& !typeMap.containsKey(clazz.getName()))
							typeMap.put(clazz.getName(), clazz);
					}
					jsMethod.append(parameter).append(
							parameter.length() > 0 ? ", " : "").append(
							"callback) {\n    return Lseam.execute(this, \"")
							.append(method.getName()).append("\", [").append(
									parameter).append("], callback);\n  };\n");
					list.add(jsMethod.toString());
				}
			}
			list.add("};\n");
			list.add("Lseam.type." + componentName + ".__name = \""
					+ componentName + "\";\n");
			list.add("Lseam.setComponent(Lseam.type." + componentName
					+ ");\n\n");
			componentMap.put(componentName, null);
		}
		Iterator<Entry<String, Class<?>>> iterator = typeMap.entrySet()
				.iterator();
		while (iterator.hasNext()) {
			Class<?> type = iterator.next().getValue();
			list.add("Lseam.type." + type.getName().replace(".", "$")
					+ " = function() {\n");
			Field[] fields = type.getDeclaredFields();
			for (Field field : fields)
				list.add("  this." + field.getName() + " = undefined;\n");
			for (Field field : fields)
				list.add("  Lseam.type."
						+ type.getName().replace(".", "$")
						+ ".prototype.get"
						+ field.getName().replaceFirst(
								field.getName().substring(0, 1),
								field.getName().substring(0, 1).toUpperCase())
						+ " = function() { return this." + field.getName()
						+ "; };\n");
			for (Field field : fields)
				list.add("  Lseam.type."
						+ type.getName().replace(".", "$")
						+ ".prototype.set"
						+ field.getName().replaceFirst(
								field.getName().substring(0, 1),
								field.getName().substring(0, 1).toUpperCase())
						+ " = function(" + field.getName() + ") { this."
						+ field.getName() + " = " + field.getName() + "; };\n");
			list.add("};\n");
			list.add("Lseam.type." + type.getName().replace(".", "$")
					+ ".__name = \"" + type.getName() + "\";\n");
			list.add("Lseam.setType(Lseam.type."
					+ type.getName().replace(".", "$") + ");\n\n");
		}
		return list;
	}

	private Boolean isBasicType(Class<?> clazz) {
		if (clazz.isPrimitive()
				|| (clazz.isArray() && (clazz == char[].class
						|| clazz == boolean[].class || clazz == byte[].class
						|| clazz == double[].class || clazz == float[].class
						|| clazz == int[].class || clazz == long[].class || clazz == short[].class)))
			return true;
		if (clazz == String.class || clazz == String[].class
				|| clazz == Character.class || clazz == Character[].class
				|| clazz == Boolean.class || clazz == Boolean[].class
				|| clazz == Byte.class || clazz == Byte[].class
				|| clazz == Double.class || clazz == Double[].class
				|| clazz == Float.class || clazz == Float[].class
				|| clazz == Integer.class || clazz == Integer[].class
				|| clazz == Long.class || clazz == Long[].class
				|| clazz == Short.class || clazz == Short[].class
				|| clazz == Number.class || clazz == Number[].class)
			return true;
		return false;
	}

	private String getReturnValue(Object obj) {
		StringBuffer sb = new StringBuffer("{\"value\": ");
		if (obj == null)
			sb.append("null");
		else if (obj instanceof String)
			sb.append("\"").append(obj).append("\"");
		else if (obj instanceof Number)
			sb.append(obj);
		else if (obj instanceof Boolean)
			sb.append(obj);
		else if (obj instanceof Character)
			sb.append(obj);
		else if (obj.getClass().isArray())
			sb.append(JSONArray.fromObject(obj).toString());
		else
			sb.append(JSONObject.fromObject(obj).toString());
		return sb.append("}").toString();
	}

	private Object[] getArrayValue(Class<?> clazz, Object[] obj) {
		if (clazz == String[].class) {
			String[] o = new String[obj.length];
			for (int i = 0; i < obj.length; i++)
				o[i] = (String) obj[i];
			return o;
		} else if (clazz == Integer[].class) {
			Integer[] o = new Integer[obj.length];
			for (int i = 0; i < obj.length; i++)
				o[i] = (Integer) obj[i];
			return o;
		} else if (clazz == Long[].class) {
			Long[] o = new Long[obj.length];
			for (int i = 0; i < obj.length; i++)
				o[i] = (Long) obj[i];
			return o;
		} else if (clazz == Double[].class) {
			Double[] o = new Double[obj.length];
			for (int i = 0; i < obj.length; i++)
				o[i] = (Double) obj[i];
			return o;
		} else if (clazz == Float[].class) {
			Float[] o = new Float[obj.length];
			for (int i = 0; i < obj.length; i++)
				o[i] = (Float) obj[i];
			return o;
		} else if (clazz == Short[].class) {
			Short[] o = new Short[obj.length];
			for (int i = 0; i < obj.length; i++)
				o[i] = (Short) obj[i];
			return o;
		} else if (clazz == Number[].class) {
			Number[] o = new Number[obj.length];
			for (int i = 0; i < obj.length; i++)
				o[i] = (Number) obj[i];
			return o;
		} else if (clazz == Byte[].class) {
			Byte[] o = new Byte[obj.length];
			for (int i = 0; i < obj.length; i++)
				o[i] = (Byte) obj[i];
			return o;
		} else if (clazz == Boolean[].class) {
			Boolean[] o = new Boolean[obj.length];
			for (int i = 0; i < obj.length; i++)
				o[i] = (Boolean) obj[i];
			return o;
		} else if (clazz == Character[].class) {
			Character[] o = new Character[obj.length];
			for (int i = 0; i < obj.length; i++)
				o[i] = (Character) obj[i];
			return o;
		}
		return obj;
	}

}